package com.accesshollywood.indexables;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.SwingWorker;

import org.joda.time.ReadableDateTime;

import sqlUtility.SQLUtility;

import com.accesshollywood.SQL.GFXSQL;
import com.accesshollywood.SQL.GrfxDateTime;
import com.accesshollywood.grfxOrdering.basics.BLOCK;
import com.accesshollywood.grfxOrdering.basics.Destinations;
import com.accesshollywood.grfxOrdering.basics.GraphicTypes;
import com.accesshollywood.grfxOrdering.basics.ShowNames;
import com.accesshollywood.grfxOrdering.basics.Users;

public abstract class Indexed implements Indexable {
	protected int							id					= -9999;
	protected String						tableName			= "";
	protected String						idColumnName		= "";
	protected LinkedHashMap<Field, String>	elementColumnName	= new LinkedHashMap<Field, String>();
	protected Object						that;
	protected boolean						isDirty				= true;
	protected boolean						isIndexed			= false;

	public Indexed() {
		initSQL();
	}

	public void setThat(Object that) {
		this.that = that;
	}

	public Indexed(int id) {
		setId(id);
	}

	public String toString() {
		return "";
	}

	public void fromString(String str) {

	}

	public synchronized int getId() {
		return id;
	}

	public synchronized void setId(int id) {
		this.id = id;
		if (id != -9999) {
			setIndexed(true);
		} else {
			setIndexed(false);
		}
	}

	public synchronized boolean isIndexed() {
		return isIndexed;
	}

	public synchronized void setIndexed(boolean isIndexed) {
		this.isIndexed = isIndexed;
	}

	public Field getField(String fieldName) {
		try {
			Class<? extends Object> c = that.getClass();
			return that.getClass().getDeclaredField(fieldName);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			try {
				return that.getClass().getSuperclass().getDeclaredField(fieldName);
			} catch (SecurityException e1) {
				e1.printStackTrace();
			} catch (NoSuchFieldException e1) {
				e1.printStackTrace();
			}
		}
		return null;
	}

	public String getSQLColumn(String fieldName) {
		Field f = getField(fieldName);
		return elementColumnName.get(f);
	}

	@Override
	public int toSQL() {
		if (this.getId() != -1) {
			if (!this.isIndexed()) {
				try {
					this.setId(SQLUtility.getNextAvailableID(tableName));
					GFXSQL.addObject(tableName, idColumnName, this.getId());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			Set<Field> fields = elementColumnName.keySet();
			Iterator<Field> itr = fields.iterator();
			while (itr.hasNext()) {
				Field field = (Field) itr.next();
				setSQLField(field);
			}
		}
		return this.getId();
	}

	public void setSQLField(Field field) {
		String SQLcolumn = elementColumnName.get(field);
		field.setAccessible(true);
		try {
			if (field.getType().isPrimitive() || field.getType().equals(String.class)) {
				GFXSQL.alterObject(tableName, SQLcolumn, field.get(this), this.getId());
			} else if (field.getType() == GrfxDateTime.class) {
				GFXSQL.alterObject(tableName, SQLcolumn, ((GrfxDateTime) field.get(this)).toSQL(), this.getId());
			} else if (field.getType() == User.class) {
				GFXSQL.alterObject(tableName, SQLcolumn, Users.currentUser.getId(), this.getId());
			} else if (field.getType().isAssignableFrom(Set.class) || Set.class.isAssignableFrom(field.getType())) {
				LinkedHashSet<?> tempSet = (LinkedHashSet<?>) field.get(this);
				Iterator<?> itr2 = tempSet.iterator();
				while (itr2.hasNext()) {
					Indexed obj = (Indexed) itr2.next();
					int id = obj.toSQL();
					// field.set(that, id);
				}
			} else if (field.getType().isAssignableFrom(Map.class) || Map.class.isAssignableFrom(field.getType())) {
				LinkedHashMap<?, ?> tempMap = (LinkedHashMap<?, ?>) field.get(this);
				Iterator<?> itr2 = tempMap.values().iterator();
				while (itr2.hasNext()) {
					Indexed obj = (Indexed) itr2.next();
					int id = obj.toSQL();
					// field.set(that, id);
				}
			} else if (field.getType().isAssignableFrom(Indexed.class) || Indexed.class.isAssignableFrom(field.getType())) {
				int id = ((Indexed) field.get(that)).toSQL();
				GFXSQL.alterObject(tableName, SQLcolumn, id, this.getId());
				// field.set(that, id);
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	public void getSQLField(Field field){
		String SQLcolumn = elementColumnName.get(field);
		// System.out.println(field.getType().getName());
		try {
			Object[] values = GFXSQL.getObject(tableName, SQLcolumn, idColumnName + "=" + getId());
			for (Object value : values) {
				if (value != null) {
					field.setAccessible(true);

					if (field.getType() == Byte.TYPE) {
						field.set(that, Byte.parseByte((String) value));
					} else if (field.getType() == Short.TYPE) {
						field.set(that, Short.parseShort((String) value));
					} else if (field.getType() == Integer.TYPE) {
						field.set(that, Integer.parseInt((String) value));
					} else if (field.getType() == Long.TYPE) {
						field.set(that, Long.parseLong((String) value));
					} else if (field.getType() == Float.TYPE) {
						field.set(that, Float.parseFloat((String) value));
					} else if (field.getType() == Double.TYPE) {
						field.set(that, Double.parseDouble((String) value));
					} else if (field.getType() == Boolean.TYPE) {
						if (((String) value).equals("0")) {
							field.set(that, false);
						} else {
							field.set(that, true);
						}
					} else if (field.getType() == String.class) {
						field.set(that, value);
					} else if (field.getType() == User.class) {
						int userId = Integer.parseInt((String) value);
						User user = Users.allUsers.get(userId);
						field.set(that, user);
					} else if (field.getType() == GrfxDateTime.class) {// field.getType().isAssignableFrom(ReadableDateTime.class) || ReadableDateTime.class.isAssignableFrom(field.getType())) {
						long millis = Long.parseLong((String) value);
						GrfxDateTime dateTime = new GrfxDateTime(millis);
						field.set(that, dateTime);
					} else if (field.getType() == ShowName.class) {
						int showNameId = Integer.parseInt((String) value);
						ShowName showName = ShowNames.allShowNames.get(showNameId);
						field.set(that, showName);
					} else if (field.getType() == GraphicType.class) {
						int graphicTypeId = Integer.parseInt((String) value);
						GraphicType graphicType = GraphicTypes.allGraphicTypes.get(graphicTypeId);
						field.set(that, graphicType);
					} else if (field.getType() == Destination.class) {
						int destinationId = Integer.parseInt((String) value);
						Destination destination = Destinations.allDestinations.get(destinationId);
						field.set(that, destination);
					} else if (field.getType() == BLOCK.class) {
						BLOCK block = BLOCK.fromString((String) value);
						field.set(that, block);
					} else if (field.getType() == ViewFilterPreset.class) {
						int presetId = Integer.parseInt((String) value);
						// ViewFilterPreset preset = ViewFilterPreset.fromString((String) value);
						// field.set(that, preset);
					} else if (field.getType().isAssignableFrom(Indexed.class) || Indexed.class.isAssignableFrom(field.getType())) {
						int id = Integer.parseInt((String) value);
						if (id != -9999) {
							try {
								Indexed obj = (Indexed) instanceOf(field.getType());
								obj.fromSQL(id);
								field.set(that, obj);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					} else if (field.getType().isAssignableFrom(Set.class) || Set.class.isAssignableFrom(field.getType())) {
						int id = Integer.parseInt((String) value);
						LinkedHashSet<Indexed> tempSet = (LinkedHashSet<Indexed>) field.get(Indexed.this);
						try {
							Type type = field.getGenericType();
							ParameterizedType pt = (ParameterizedType) type;
							Type type2 = pt.getActualTypeArguments()[0];
							Indexed obj = (Indexed) instanceOf(type2);
							obj.fromSQL(id);
							tempSet.add(obj);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}

					else {
						// System.out.println("ACK!!!! I DON'T KNOW WHAT THIS IS!!!");
					}

					// if (field.getType().isAssignableFrom(Indexed.class) || Indexed.class.isAssignableFrom(field.getType())) {
					// ((Indexed) field.get(that)).fromSQL();
					// }
				}
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}

	@Override
 	public void fromSQL() {
		FromSQLWorker fromSQLWorker = new FromSQLWorker();
		try {
			fromSQLWorker.doInBackground();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void fromSQL(int id) {
		this.setId(id);
		FromSQLWorker fromSQLWorker = new FromSQLWorker();
		try {
			fromSQLWorker.doInBackground();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static <T> T instanceOf(Type type) throws Exception {
		Class<T> clazz = (Class<T>) type;
		return clazz.newInstance();
	}

	public static <T> T instanceOf(Class<T> clazz) throws Exception {
		return clazz.newInstance();
	}

	public synchronized boolean isDirty() {
		return isDirty;
	}

	public synchronized void setDirty(boolean isDirty) {
		this.isDirty = isDirty;
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((elementColumnName == null) ? 0 : elementColumnName.hashCode());
		result = prime * result + id;
		result = prime * result + ((idColumnName == null) ? 0 : idColumnName.hashCode());
		result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
		return result;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Indexed other = (Indexed) obj;

		if (elementColumnName == null) {
			if (other.elementColumnName != null)
				return false;
		} else if (!elementColumnName.equals(other.elementColumnName))
			return false;
		if (id != other.id)
			return false;
		if (idColumnName == null) {
			if (other.idColumnName != null)
				return false;
		} else if (!idColumnName.equals(other.idColumnName))
			return false;
		if (tableName == null) {
			if (other.tableName != null)
				return false;
		} else if (!tableName.equals(other.tableName))
			return false;
		return true;
	}

	public class FromSQLWorker extends SwingWorker {
		@Override
		protected Object doInBackground() throws Exception {
			// if (isDirty) {
			if (isIndexed()) {
				Set<Field> fields = elementColumnName.keySet();
				Iterator<Field> itr = fields.iterator();
				while (itr.hasNext()) {
					Field field = (Field) itr.next();
					getSQLField(field);
				}
				isDirty = false;
			}
			return null;
		}

	}
}
